#ifndef EXTENDIBLEHASH_H
#define EXTENDIBLEHASH_H

#include <cstdint>
#include <vector>
#include <string>
#include <sstream>

#include "../persistencia/file.h"
#include "bucket.h"

struct DirectoryEntry {
    u_int8_t depth;
    u_int32_t fileIndex;
}__attribute__((packed));


class Header : public Serializable {
    public:
        virtual void read(BinaryStream& in) {
            globalDepth = in.readPrimitive<u_int8_t>();
            directory = in.readPrimitives<DirectoryEntry>();
        }
        virtual void write(BinaryStream& out) {
            out.writePrimitive<u_int8_t>(globalDepth);
            out.writePrimitives<DirectoryEntry>(directory);
        }
        virtual std::string toString() {
           std::stringstream sout;
           sout << "(Table) global depth: " << (u_int16_t) globalDepth << " table: ";
           for (DirectoryEntry entry : directory) {
               sout << '(' << (u_int16_t) entry.depth << ") " << entry.fileIndex << " ";
           }
           return sout.str();
        }

        std::vector<DirectoryEntry> directory;
        u_int8_t globalDepth;
};

template<class Key, class Record>
class ExtendibleHash {
    public:
        ExtendibleHash();
        virtual ~ExtendibleHash();

        void addMetadata(Serializable* metadata);

        bool create(const std::string &filename, u_int32_t bucketSize);
        bool open(const std::string &filename, u_int32_t bucketSize);

        bool contains(Key key);
        Nullable<Record>  get(Key key);
        bool add(Record record);
        bool update(Record record);
        bool remove(Key key);

        virtual std::string toString();

    private:
        Header header;
        u_int32_t bucketSize;
        File file;
        std::string logFileName;

        Bucket<Key, Record> readBucket(u_int32_t index);
        u_int32_t getDirectoryIndex(u_int32_t hash) const;
        bool internalAdd(Record record, u_int32_t index, Bucket<Key, Record>& bucket);
        bool handleOverflow(Record newRecord, Bucket<Key, Record>& fullBucket, u_int32_t fullBucketIndex);
        void extendTable();

        void tryMerge(u_int32_t index, Bucket<Key, Record>& bucket);
        bool tryCollapse();
};

#endif // EXTENDIBLEHASH_H
